#ifndef __Math_hpp__
#define __Math_hpp__

#include "Utils.hpp"

namespace ge {

//----------------------------------------------------------------------------//
// Math
//----------------------------------------------------------------------------//

namespace Math
{

     static const float Pi        = 3.1415926535897932384626433832795f;
     static const float DoublePi  =  6.283185307179586476925286766559f;
     static const float HalfPi    =  1.5707963267948966192313216916398f;
     static const float QuadPi    =  0.78539816339744830961566084581988f;
     static const float Degrees = 57.295779513082320876798154814105f;
     static const float Radians =  0.01745329251994329576923690768489f;
     static const float Epsilon = 1e-6f;
     static const float EpsilonSq = 1e-12f;
     static const uint RandMax = 0x00ffffff;
     static const float LogTwo = logf( 2.0f );
     static const float MinFloat = FLT_MIN;
     static const float MaxFloat = FLT_MAX;

     template < class T > T Sqr( const T& _x );

     float Sqrt( float _x );

     float RSqrt( float _x );

     float Sin( float _x );

     float Cos( float _x );

     void SinCos( float _a, float& _s, float& _c );

     int Abs( int _value );

     float Abs( float _value );

     float Exp( float _x );

     float Log( float _x );

     float LogN( float _base, float _x );

     float Log2( float _x );

     float Tan( float _x );

     float ATan( float _x );

     float ATan2( float _x, float _y );

     float FastACos( float _x );

     float ACos( float _x );

     float FastASin( float _x );

     float ASin( float _x );

     float Floor( float _x );

     float Ceil( float _x );

     float Mod( float _x, float _y );

     bool Equals( float _a, float _b, float _tolerance = 0.0f );

     uint Random( uint32& _value );

     int SignedRandom( uint32& _value );

     uint Random( void ); // 0..RandMax

     int SignedRandom( void ); // -(RandMax/2)..(RandMax/2)

     uint LimitRandom( uint _max );

     float UnitRandom( void );

     float SymmetricRandom( void );

     float RangeRandom( float _low, float _high );

     float ToRadians( float _a );

     float ToDegrees( float _a );

     float MakeFloat( uint _m, uint _e, uint _s );

     uint32 FirstPow2( uint32 _val );

     template < typename T > bool IsPow2( T _val );

     float FixedToFloat( uint32 _value, uint32 _bits, float _default = 0.0f );

     uint32 FloatToFixed( float _value, uint32 _bits );

     uint32 FixedToFixed( uint32 _value, uint32 _from, uint32 _to );

     uint32 ReadInt( const void* _src, uint32 _size );

     void WriteInt( void* _dst, uint32 _value, size_t _size );

     uint32 Mask( uint32 _shift, uint32 _bits );

     template < class T > T Clamp( const T& _val, const T& _min, const T& _max );

     template < class X, class T > X Mix( const X& _a, const X& _b, const T& _t );

     template < class X, class T > X Lerp( const X& _a, const X& _b, const T& _t );

     template < class X, class T > X Cerp( const X& _a, const X& _b, const T& _t );

     template < class X, class T > X Herp3( const X& _a, const X& _b, const T& _t );

     template < class X, class T > X Herp5( const X& _a, const X& _b, const T& _t );

     template < class E, class X > X Step( const E& _edge, const X& _x );

     template < class E, class X > X SmoothStep( const E& _edge0, const E& _edge1, const X& _x );

} // namespace Math


//----------------------------------------------------------------------------//
// Math
//----------------------------------------------------------------------------//

namespace Math {

//----------------------------------------------------------------------------//
template < class T >
inline T Sqr( const T& _x )
{
     return _x * _x;
}
//----------------------------------------------------------------------------//
inline float Sqrt( float _x )
{
     return sqrtf( _x );
}
//----------------------------------------------------------------------------//
inline float RSqrt( float _x )
{
     return 1.0f / Sqrt( _x );
}
//----------------------------------------------------------------------------//
inline float Sin( float _x )
{
     return sinf( _x );
}
//----------------------------------------------------------------------------//
inline float Cos( float _x )
{
     return cosf( _x );
}
//----------------------------------------------------------------------------//
inline void SinCos( float _a, float& _s, float& _c )
{
     _s = Sin( _a );
     _c = Cos( _a );
}
//----------------------------------------------------------------------------//
inline int Abs( int _value )
{
     return abs( _value );
}
//----------------------------------------------------------------------------//
inline float Abs( float _value )
{
     return fabsf( _value );
}
//----------------------------------------------------------------------------//
inline float Exp( float _x )
{
     return expf( _x );
}
//----------------------------------------------------------------------------//
inline float Log( float _x )
{
     return logf( _x );
}
//----------------------------------------------------------------------------//
inline float LogN( float _base, float _x )
{
     return Log( _x ) / Log( _base );
}
//----------------------------------------------------------------------------//
inline float Log2( float _x )
{
     return LogN( LogTwo, _x );
}
//----------------------------------------------------------------------------//
inline float Tan( float _x )
{
     return tanf( _x );
}
//----------------------------------------------------------------------------//
inline float ATan( float _x )
{
     return atanf( _x );
}
//----------------------------------------------------------------------------//
inline float ATan2( float _x, float _y )
{
     return atan2f( _x, _y );
}
//----------------------------------------------------------------------------//
inline float FastACos( float _x )
{
     return acosf( _x );
}
//----------------------------------------------------------------------------//
inline float ACos( float _x )
{
     if( _x > -1.0f )
     {
          if( _x < 1.0f ) return FastACos( _x );
          return 0.0f;
     }
     return Pi;
}
//----------------------------------------------------------------------------//
inline float FastASin( float _x )
{
     return asinf( _x );
}
//----------------------------------------------------------------------------//
inline float ASin( float _x )
{
     if( _x > -1.0f )
     {
          if( _x < 1.0f ) return FastASin( _x );
          return HalfPi;
     }
     return -HalfPi;
}
//----------------------------------------------------------------------------//
inline float Floor( float _x )
{
     return floorf( _x );
}
//----------------------------------------------------------------------------//
inline float Ceil( float _x )
{
     return ceilf( _x );
}
//----------------------------------------------------------------------------//
inline float Mod( float _x, float _y )
{
     return fmodf( _x, _y );
}
//----------------------------------------------------------------------------//
inline bool Equals( float _a, float _b, float _tolerance )
{
     return Abs( _b - _a ) <= _tolerance;
}
//----------------------------------------------------------------------------//
inline uint LimitRandom( uint _max )
{
     return Random() % ( _max += (_max==0) );
}
//----------------------------------------------------------------------------//
inline float UnitRandom( void )
{
     return ((float)Random()) * ( 1.0f / 16777215.f );
}
//----------------------------------------------------------------------------//
inline float SymmetricRandom( void )
{
     return ((float)SignedRandom()) * ( 1.0f / 16777215.f );
}
//----------------------------------------------------------------------------//
inline float RangeRandom( float _low, float _high )
{
     return ( _high - _low ) * UnitRandom() + _low;
}
//----------------------------------------------------------------------------//
inline float ToRadians( float _a )
{
     return _a * Radians;
}
//----------------------------------------------------------------------------//
inline float ToDegrees( float _a )
{
     return _a * Degrees;
}
//----------------------------------------------------------------------------//
inline float MakeFloat( uint _m, uint _e, uint _s )
{
     // mantissa : 23 bit
     // exponent :  8 bit
     // sign     :  1 bit

     union { uint u; float f; } f = { ( _m & 0x007fffff ) | ( ( _e & 0x000000ff ) << 23 ) | ( ( _s & 0x000000001 ) << 31 ) };
     return f.f;       
}
//----------------------------------------------------------------------------//
inline uint32 FirstPow2( uint32 _val )
{
     --_val;
     _val |= _val >> 16;
     _val |= _val >> 8;
     _val |= _val >> 4;
     _val |= _val >> 2;
     _val |= _val >> 1;
     return ++ _val;
}
//----------------------------------------------------------------------------//
template < typename T >
inline bool IsPow2( T _val )
{
     return ( _val & ( _val - 1 ) ) == 0;
}
//----------------------------------------------------------------------------//
inline float FixedToFloat( uint32 _value, uint32 _bits, float _default )
{
     if( _bits == 32 ) { -- _bits; }
     return ( _bits != 0 ? ((float)_value ) / ((float)(( 1u << _bits ) - 1u) ) : _default );
}
//----------------------------------------------------------------------------//
inline uint32 FloatToFixed( float _value, uint32 _bits )
{
     if( _bits == 32 ) { -- _bits; }
     if( _value <= 0.0f ) return 0;
     if( _value >= 1.0f ) return ( 1u << _bits ) - 1u;
     return (uint32)( _value * (float)( 1u << _bits ) );
}
//----------------------------------------------------------------------------//
inline uint32 FixedToFixed( uint32 _value, uint32 _from, uint32 _to )
{  
     if( _from == 32 ) { -- _from; }
     if( _to == 32 ) { -- _to; }
     
     if( _from > _to )
     {
          _value >>= _from - _to;
     }
     else if( _from < _to && _value != 0 )
     {
          uint32 _max =  ( 1u << _from ) - 1u;

          if( _value == _max ) _value = ( 1u << _to ) - 1u;
          else if( _max > 0 ) _value *= (( 1u << _to ) / _max);
          else _value = 0;
     }
     
     return _value;
}
//----------------------------------------------------------------------------//
inline uint32 ReadInt( const void* _src, uint32 _size )
{
     switch( _size )
     {
          case 1 : return ((const uint8*) _src )[0];
          case 2 : return ((const uint16*) _src )[0];
          case 3 :
          {
               uint32 _bytes[ 3 ] = 
               {
                    ((const uint8*) _src )[0],
                    ((const uint8*) _src )[1],
                    ((const uint8*) _src )[2],
               };

               return _bytes[0] | ( _bytes[1] << 8 ) | ( _bytes[2] << 16 );
          }
          case 4 : return ((const uint32*) _src )[0];
     }

     return 0;
}
//----------------------------------------------------------------------------//
inline void WriteInt( void* _dst, uint32 _value, size_t _size )
{
     switch( _size )
     {
          case 1 : ((uint8*) _dst )[0] = ((uint8) _value ); break;
          case 2 : ((uint16*) _dst )[0] = ((uint16) _value ); break;
          case 3 :
          {
               ((uint8*) _dst )[0] = (uint8)( _value & 0xff );
               ((uint8*) _dst )[1] = (uint8)( ( _value >> 8 ) & 0xff );
               ((uint8*) _dst )[2] = (uint8)( ( _value >> 16 ) & 0xff );

          } break;

          case 4 :((uint32*) _dst )[0] = ((uint16) _value ); break;
     };
}
//----------------------------------------------------------------------------//
inline uint32 Mask( uint32 _shift, uint32 _bits )
{
     return  ( ( 0xffffffff >> ( 32u - _bits ) ) << _shift );
}
//----------------------------------------------------------------------------//
template < class T >
inline T Clamp( const T& _val, const T& _min, const T& _max )
{
     assert( _min <= _max );
     return Max( _min, Min( _val, _max ) );
}
//----------------------------------------------------------------------------//
template < class X, class T >
inline X Mix( const X& _a, const X& _b, const T& _t )
{
     return _a + ( _b - _a ) * _t;
}
//----------------------------------------------------------------------------//
template < class X, class T >
inline X Lerp( const X& _a, const X& _b, const T& _t )
{
     return Mix( _a, _b, _t );
}
//----------------------------------------------------------------------------//
template < class X, class T >
inline X Cerp( const X& _a, const X& _b, const T& _t )
{
     return Mix( _a, _b, ( T(1) - Cos( _t * Pi ) ) / T(2) );
}
//----------------------------------------------------------------------------//
template < class X, class T >
inline X Herp3( const X& _a, const X& _b, const T& _t )
{
     return Mix( _a, _b, _t * _t * ( T(3) - T(2) * _t ) );
}
//----------------------------------------------------------------------------//
template < class X, class T >
inline X Herp5( const X& _a, const X& _b, const T& _t )
{
     return Mix( _a, _b, _t * _t * _t * ( T(10) + _t * (T(6) * _t - T(15) ) ) );
}
//----------------------------------------------------------------------------//
template < class E, class X >
inline X Step( const E& _edge, const X& _x )
{
     return ( _x < _edge ? X(0) : X(1) );
}
//----------------------------------------------------------------------------//
template < class E, class X >
inline X SmoothStep( const E& _edge0, const E& _edge1, const X& _x )
{
     X _t = Clamp< E >( ( _x - _edge0 ) / ( _edge1 - _edge0 ), X(0), X(1) );
     return _t * _t * ( X(3) - X(2) * _t )
}
//----------------------------------------------------------------------------//

} // namespace Math

} // namespace ge

#endif//__Math_hpp__
